# -*- coding: utf-8 -*-
#----------------------------------------------------------------------------#
# hanziTable.py
# Lars Yencken <lars.yencken@gmail.com>
# Zhihui Jin <zhihuijin@gmail.com>
# vim: ts=4 sw=4 sts=4 et tw=78:
# Fri Dec 22 13:17:45 2006
#
#----------------------------------------------------------------------------#

""" An interface to the hanzi <-> kanji conversion table.
    also estimate probability P(kanji | hanzi)
"""

#----------------------------------------------------------------------------#

from os import path

from www import settings
from tools.common import sopen
from tools.relation import Relation, ScoredRelation
from tools.table import parseLines

#----------------------------------------------------------------------------#

# Relevant data files.
_tableDir = path.join(settings.DATA_DIR, 'tables')

_cognateTable = path.join(_tableDir, 'kanji_hanzi_cognates')
_proofedCognates = path.join(_tableDir, 'kanji_hanzi_cognates_proofed')
_pixelMapping = path.join(_tableDir, 'kanji_hanzi_cognates_scored')
_similarMapping  = path.join(_tableDir, 'hanzi_kanji_similar_shape')
_hanzi2kanjiShapeMapping = path.join(_tableDir, 'hanzi_kanji_shape_mapping')
_kanji2hanziShapeMapping = path.join(_tableDir, 'kanji_hanzi_shape_mapping')

#----------------------------------------------------------------------------#
# PUBLIC
#----------------------------------------------------------------------------#

def getCognateRelation():
    """ Fetches the cognate correspondence between kanji and hanzi.

        @return: A Relation object.
    """
    relation = Relation('kanji', 'hanzi')
    for kanji, hanzi in parseLines(sopen(_cognateTable)):
        relation.add(kanji, hanzi)

    return relation

#----------------------------------------------------------------------------#

def getHanziToKanjiShapeMapping():
    """ Fetches the shape mapping from hanzi to kanji,
    """
    return _getShapeMapping ('hanzi', 'kanji', _hanzi2kanjiShapeMapping)

#----------------------------------------------------------------------------#

def getKanjiToHanziShapeMapping():
    """ Fetches the shape mapping from kanji to hanzi
    """
    return _getShapeMapping ('kanji', 'hanzi', _kanji2hanziShapeMapping)

#----------------------------------------------------------------------------#

def getHanziToKanjiProbTable():
    """ Estimate  P(kanji | hanzi), the parameters in unigram model to do
        hanzi-to-kanji mapping, one-to-one mapped pairs are ignored
    """
    probTable = {}
    hanziTable = getHanziToKanjiTable()
    kanjiCountTable = _getCountTable(_kanjiCountTable)

    #use default value to do smoothing
    _default = 1.0 / 10000

    for hanzi in hanziTable.keys():
        kanjis = hanziTable[hanzi]

        #skip if one-to-one mapping
        if len(kanjis) == 1:
            continue

        sum = 0.0
        for kanji in kanjis:
            count = kanjiCountTable.get(kanji, _default)
            sum += count

        kanjisProb = {}
        for kanji in kanjis:
            prob = kanjiCountTable.get(kanji, _default) / float(sum)
            kanjisProb[kanji] = prob

        probTable[hanzi] = kanjisProb

    return probTable

#----------------------------------------------------------------------------#

def getSimilarityExtendedCognates():
    """ Builds a Relation between kanji and hanzi, extended by the similarity
        pairs provided by Jin.
    """
    relation = getCognateRelation()

    for hanzi, kanji in parseLines(sopen(_similarMapping)):
        relation.add(kanji, hanzi)

    return relation

#----------------------------------------------------------------------------#

# The proofing codes are still under construction, more categories will be
# added later
_validProofingCodes = ['p', 'q', 'r', 'u']

def getProofedCognateRelation(code):
    """ Provides a proofed mapping form kanji to hanzi. The quality can be
        fine-tuned using the code parameter.

        @param code: The proofing code to use. One of 'p', 'q', 'r' or 'u'.
    """
    if not code in _validProofingCodes:
        raise ValueError, "Proofing code %s is not valid" % code

    # Accept the given code, and anything better than it.
    selectedCodes = _validProofingCodes[:_validProofingCodes.index(code)+1]

    relation = Relation('kanji', 'hanzi')
    for kanji, hanzi, similarity, code in parseLines(sopen(_proofedCognates)):
        if code in selectedCodes:
            relation.add(kanji, hanzi)

    return relation

#----------------------------------------------------------------------------#

def getCognatePixelRelation():
    """ Fetches a conversion table from hanzi to kanji which contains pixel
        similarity weights. Fetches the full table.

        @return: A ScoredRelation object.
    """
    scoredRelation = ScoredRelation('kanji', 'hanzi')
    for kanji, hanzi, similarity in parseLines(sopen(_pixelMapping)):
        scoredRelation.add(kanji, hanzi, float(similarity))

    return scoredRelation

#----------------------------------------------------------------------------#
# PRIVATE
#----------------------------------------------------------------------------#

def _getCountTable(countFile):
    """ Read kanji/hanzi counts table, ignore the symbol if 'count == 0'
    """
    table = {}
    iStream = sopen(countFile)
    for line in iStream:
        symbol, count = line.rstrip().split()
        count = int(count)

        #skip if count is zeno
        if count != 0:
            table[symbol] = count

    iStream.close()

    return table 


#----------------------------------------------------------------------------#

def _getShapeMapping(fromSet, toSet, mappingTable):
    """ Fetches the shape mapping from fromSet to toSet, specified in
    mappingTable, the mapping is not symetric here, so only the forward
    mapping in the Relation object will be useful 
    """
    remapping = {}
    relation = Relation(fromSet, toSet)
    for line in sopen(mappingTable):
        if line.startswith('#'):
            continue

        items    = line.split();
        level    = items[0]
        fromChar = items[1]
        mapSet   = set()

        if level == 'm':
            fromChar = fromChar[0]
            for item in items[2:]:
                toChar = item[0] 
                label  = item[1:]
                if label == 'm':
                    mapSet.union(remapping[toChar])
                else:
                    mapSet.add(toChar)

            remapping[fromChar] = mapSet

        else:
            for item in items[2:]:
                toChar = item[0]
                label  = item[1:]
                if label == 'm':
                    mapSet.union(remapping[toChar])
                else:
                    mapSet.add(toChar)
            
            for toChar in mapSet:
                relation.add(fromChar, toChar)

    return relation


#----------------------------------------------------------------------------#
# vim: ts=4 sw=4 sts=4 et tw=78:
